/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package Communication;

import java.util.regex.*;

/**A line-by-line stateful pattern matcher.  <P>LineGrepper uses one or more
 * regular expression patterns to match a sequence of patterns in lines of text.
 * Each line of incoming text is matched against the current pattern.  When a
 * match is found, the current pattern is changed to the next pattern.  Once the
 * last pattern is reached, any further input is ignored.</P>
 *
 * </P>This matcher is not designed to ever reject the pattern set, but rather to
 * ignore any non-matching lines between matching lines.  For example, match the
 * pattern set {"firstLineToMatch","secondLineToMatch"} against the text the
 * following text:</P>
 * <PRE>
 * Garbage Line
 * Garbage Line
 * firstLineToMatch
 * Garbage Line
 * secondLineToMatch</PRE>
 * <P>The text would ultimately match once the last line was reached, by
 * searching first for "firstLineToMatch" and finding it on the third line, then
 * searching for "secondLineToMatch" and finding it on the last line.  All other
 * lines would be ignored.</P>
 *
 * <P>This would be useful for searching lines of log text where logging
 * information from several sources is merged into one stream.  If one is
 * searching for a set of information from multiple lines with a known format,
 * a regular expression can be constructed for each line of the information set
 * and tested against each line of the log.  If the structure described by the
 * patterns is present in the log, the grepping object will capture the matched
 * patterns.</P>
 *
 * @author Aaron Heise
 */
public class LineGrepper {
    /**Contains the regular expression patterns to be matched. */
    Pattern[] linePatterns;

    /**The listener list for MatchCompleteListeners*/
    protected javax.swing.event.EventListenerList matchCompleteListenerList =
            new javax.swing.event.EventListenerList();

    /**The listener list for MatchCompleteListeners*/
    protected javax.swing.event.EventListenerList patternMatchListenerList =
            new javax.swing.event.EventListenerList();

    /**Contains the current matching state.  There is one state for each input
     * pattern.  
     * -1: uninitialized
     * 0 to (pattern_count - 1): searching
     * pattern_count: complete
     */
    int currentPatternIndex = -1;
    
    /**Contains a Matcher for each matched line.*/
    Matcher[] matchingMatchers = null;

    /**The current state matcher.*/
    Matcher currentMatcher;

    /**Creates a LineGrepper with an array of patterns.  Patterns are compiled
     * immediately to ensure they are syntactically correct before processing
     * begins.
     * @param linePatternStrings The line-by-line pattern strings
     * @throws PatternSyntaxException at least one pattern string contains a syntactical error
     */
    public LineGrepper(String[] linePatternStrings) throws PatternSyntaxException {
        this.matchingMatchers = new Matcher[linePatternStrings.length];
        this.linePatterns = new Pattern[linePatternStrings.length];
        for(int i = 0; i < linePatternStrings.length; i++) {
            this.linePatterns[i] = Pattern.compile(linePatternStrings[i]);
        }
        this.nextPattern();
    }

    /**Switches to the next pattern.  Clears the state matcher if matching
     * is complete.
     */
    protected void nextPattern() {
        this.currentPatternIndex++;
        if(this.isMatchingComplete()) {
            this.currentMatcher = null;
        } else {
            this.currentMatcher = this.linePatterns[this.currentPatternIndex].matcher("");
        }
    }

    /**Checks to see if all patterns have been found (in order).
     *
     * @return true if all patterns have been found.
     */
    public boolean isMatchingComplete() {
        return this.currentPatternIndex >= 0 && this.currentPatternIndex == this.linePatterns.length;
    }

    /**Processes one line of input.  If the input line ends with a newline (\n),
     * it is stripped.
     * @param lineOfText The line of text to check against the current pattern
     * @return true if matching is complete (see {@link Communication.LineGrepper#isMatchingComplete isMatchingComplete()})
     */
    public boolean inputLine(String lineOfText) {
        if(this.isMatchingComplete()) {
            return true;
        } else {
            this.currentMatcher.reset(LineGrepper.stripNewlineAtEndOfLine(lineOfText));
            if(this.currentMatcher.matches()) {
                this.matchingMatchers[this.currentPatternIndex] = this.currentMatcher;
                this.firePatternMatchEvent(new LineGrepperPatternMatchEvent(this, this.currentMatcher));
                this.nextPattern();
                if(this.isMatchingComplete()) {
                    this.fireMatchCompleteEvent(new LineGrepperMatchCompleteEvent(this, this.matchingMatchers));
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        }
    }

    /**Strips a newline character from the end of a string if present.
     * @param lineOfText string to check and strip
     * @return the string after being stripped.
     */
    protected static String stripNewlineAtEndOfLine(String lineOfText) {
        int lineOfTextLength = lineOfText.length();
        if(lineOfTextLength > 0 && lineOfText.charAt(lineOfTextLength - 1) == '\n') {
            return lineOfText.substring(0, lineOfTextLength - 1);
        } else {
            return lineOfText;
        }
    }

    /**Returns the list of matchers that captured the match of each pattern.
     * This allows the captured text of each group in the input patterns to be
     * extracted.
     * @return an array of Matchers (see {@link java.util.regex.Matcher Matcher}) corresponding to each matched pattern (see {@link Communication.LineGrepper#getPatterns() getPatterns}).
     */
    public Matcher[] getMatchingMatchers() {
        return this.matchingMatchers;
    }

    /**Get the current state of the pattern matcher.
     * @return the index of the current pattern being matched.  This index will correspond to the index of the pattern in the pattern list (see {@link Communication.LineGrepper#getPatterns() getPatterns}).
     */
    public int getCurrentPatternIndex() {
        return this.currentPatternIndex;
    }

    /**Get the number of patterns being matched.
     * @return the number of patterns being matched
     */
    public int getNumberOfPatterns() {
        return this.linePatterns.length;
    }

    /**Gets the patterns being matched.
     * @return the patterns being matched, corresponding to the pattern strings given in the constructor, in the same order.
     */
    public Pattern[] getPatterns() {
        return this.linePatterns;
    }

    /**Add a listener that will be notified when this grepper has completed matching.
     * @param listener listener to add
     */
    public void addMatchCompleteListener(LineGrepperMatchCompleteListener listener) {
        this.matchCompleteListenerList.add(LineGrepperMatchCompleteListener.class, listener);
    }

    /**Remove a listener from being notified when this grepper has completed matching.
     * @param listener listener to remove
     */
    public void removeMatchCompleteListener(LineGrepperMatchCompleteListener listener) {
        this.matchCompleteListenerList.remove(LineGrepperMatchCompleteListener.class, listener);
    }

    /**Fires the match complete event to listeners.
     * @param evt the event to transmit
     */
    protected void fireMatchCompleteEvent(LineGrepperMatchCompleteEvent evt) {
        Object[] listeners = this.matchCompleteListenerList.getListenerList();
        for(Object listener : listeners) {
            if(listener instanceof LineGrepperMatchCompleteListener) {
                ((LineGrepperMatchCompleteListener)listener).lineGrepperMatchComplete(evt);
            }
        }
    }


     /**Add a listener that will be notified when this grepper has completed matching.
     * @param listener listener to add
     */
    public void addPatternMatchListener(LineGrepperPatternMatchListener listener) {
        this.patternMatchListenerList.add(LineGrepperPatternMatchListener.class, listener);
    }

    /**Remove a listener from being notified when this grepper has completed matching.
     * @param listener listener to remove
     */
    public void removePatternMatchListener(LineGrepperPatternMatchListener listener) {
        this.patternMatchListenerList.remove(LineGrepperPatternMatchListener.class, listener);
    }

    /**Fires the match complete event to listeners.
     * @param evt the event to transmit
     */
    protected void firePatternMatchEvent(LineGrepperPatternMatchEvent evt) {
        Object[] listeners = this.patternMatchListenerList.getListenerList();
        for(Object listener : listeners) {
            if(listener instanceof LineGrepperPatternMatchListener) {
                ((LineGrepperPatternMatchListener)listener).lineGrepperPatternMatched(evt);
            }
        }
    }
}
